package cryptography;

/**
 *
 * @author Mouchicc & Mavs
 * @version 1.0
 */
import java.math.BigInteger;
import java.security.SecureRandom;

public class RSA {

    private final static BigInteger one = new BigInteger("1");
    private final static SecureRandom random = new SecureRandom();
    public BigInteger privateKey;
    private BigInteger publicKey;
    public BigInteger mod;

    // generate an N-bit (roughly) public and private key
    RSA(int N) {
        BigInteger p = BigInteger.probablePrime(N / 2, random);
        BigInteger q = BigInteger.probablePrime(N / 2, random);
        System.out.println("p: " + p.toString() + " | q: " + q.toString());
        BigInteger phi = (p.subtract(one)).multiply(q.subtract(one));

        mod = p.multiply(q);
        publicKey = new BigInteger("65537");     // common value in practice = 2^16 + 1
        privateKey = publicKey.modInverse(phi);
        System.out.println(privateKey.toString());
    }

    //For encrypting, giving the p, q and the publickey
    RSA(BigInteger P, BigInteger Q, BigInteger PK) {
        P = P.nextProbablePrime();
        Q = Q.nextProbablePrime();

        BigInteger phi = (P.subtract(one)).multiply(Q.subtract(one));

        mod = P.multiply(Q); // N
        publicKey = PK; // e
        while (phi.gcd(publicKey).intValue() > 1) {
            publicKey = publicKey.add(new BigInteger("2"));
        }

        privateKey = publicKey.modInverse(phi); // d
    }

    RSA(int MOD, int PRIVATEKEY) {
        privateKey = new BigInteger(Integer.toString(PRIVATEKEY));
        mod = new BigInteger(Integer.toString(MOD));
    }

    RSA(BigInteger MOD, BigInteger privatekey) {
        mod = MOD;
        privateKey = privatekey;
    }

    BigInteger encrypt(String message) {
        BigInteger bi = string2int(message);
        return bi.modPow(publicKey, mod);
    }

    BigInteger enc(int i) {
        BigInteger bi = new BigInteger(Integer.toString(i));
        System.out.println("encrypt: " + bi.toString());
        return bi.modPow(publicKey, mod);
    }

    BigInteger dec(BigInteger bi) {
        return bi.modPow(privateKey, mod);
    }

    String decrypt(BigInteger encrypted) {
        BigInteger bi = encrypted.modPow(privateKey, mod);
        return int2string(bi);
    }

    @Override
    public String toString() {
        String s = "";
        s += "Public key  = " + publicKey + "\n";
        s += "Private key = " + privateKey + "\n";
        s += "Modulus = " + mod;
        return s;
    }

    /**
     * Convert a string into a BigInteger. The string should consist of ASCII
     * characters only. The ASCII codes are simply concatenated to give the
     * integer.
     */
    public static BigInteger string2int(String str) {
        byte[] b = new byte[str.length()];
        for (int i = 0; i < b.length; i++) {
            b[i] = (byte) str.charAt(i);
        }
        return new BigInteger(1, b);
    }

    /**
     * Convert a BigInteger into a string of ASCII characters. Each byte in the
     * integer is simply converted into the corresponding ASCII code.
     */
    public static String int2string(BigInteger n) {
        byte[] b = n.toByteArray();
        StringBuffer s = new StringBuffer();
        for (int i = 0; i < b.length; i++) {
            s.append((char) b[i]);
        }
        return s.toString();
    }

    /**
     * Apply RSA encryption to a string, using the key (N,e). The string is
     * broken into chunks, and each chunk is converted into an integer. Then
     * that integer, x, is encoded by computing x^e (mod N).
     */
    public BigInteger[] encode(String plaintext) {
        BigInteger N = mod;
        BigInteger e = publicKey;

        int charsperchunk = (N.bitLength() - 1) / 8;
        while (plaintext.length() % charsperchunk != 0) {
            plaintext += ' ';
        }
        int chunks = plaintext.length() / charsperchunk;
        BigInteger[] c = new BigInteger[chunks];
        for (int i = 0; i < chunks; i++) {
            String s = plaintext.substring(charsperchunk * i, charsperchunk * (i + 1));
            c[i] = string2int(s);
            c[i] = c[i].modPow(e, N);
            System.out.println(s + " | " + string2int(s));
        }
        return c;
    }

    /**
     * Apply RSA decryption to a string, using the key (N,d). Each integer x in
     * the array of integers is first decoded by computing x^d (mod N). Then
     * each decoded integers is converted into a string, and the strings are
     * concatenated into a single string.
     */
    public String decode(BigInteger[] cyphertext) {
        BigInteger N = mod;
        BigInteger d = privateKey;
        String s = "";
        for (int i = 0; i < cyphertext.length; i++) {
            s += int2string(cyphertext[i].modPow(d, N));
        }
        return s;
    }

    public String BIarrayToString(BigInteger[] bis) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bis.length; i++) {
            sb.append(bis[i] + " ");
        }
        return sb.toString();
    }
}